const PENDING = 'pending';     // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected';   // 失败

// promise 是一个类。
class MyPromise {
  // 接收一个执行器, 并立即执行
  constructor(executor) {
    // executor(this.resolve, this.reject);
    // 为了捕获到 执行器在执行的过程中出现异常. 使用 try/catch 捕获异常
    try {
      executor(this.resolve, this.reject);
    } catch (err) {
      this.reject(err);
    }
  }
  // 状态: 初始为 pending
  // 一旦确定, 就不可更改
  status = PENDING;

  // 定义两个属性：
  // 成功的值
  value = undefined;
  // 失败的原因
  reason = undefined;

  // 成功回调 & 失败回调
  // 为确保 then 方法中传入的两个回调函数为异步逻辑时
  // 在Promise 的状态为等待时, 保存多个回调
  successCallback = [];
  failCallback = [];

  resolve = value => {
    // 如果状态不是 pending, 就不能更改
    if (this.status !== PENDING) return;
    // 将状态更改为成功
    this.status = FULFILLED;
    // 保存成功的值
    this.value = value;
    // 如果成功回调存在，就执行它
    while (this.successCallback.length) {
      // 弹出第一个回调函数，并执行
      // 经过处理，再调用回调函数的时候，不需要再传值了
      this.successCallback.shift()();
    }
  }
  reject = reason => {
    // 如果状态不是 pending, 就不能更改
    if (this.status !== PENDING) return;
    // 将状态改为失败
    this.status = REJECTED;
    // 保存失败的原因
    this.reason = reason;
    // 如果失败回调存在，就执行它
    while (this.failCallback.length) {
      // 弹出第一个回调函数，并执行
      // 经过处理，再调用回调函数的时候，不需要再传值了
      this.failCallback.shift()();
    }
  }
  then(successCallback, failCallback) {
    // 判断是否有传递参数进来
    // 如果没有传入successCallback, 或者传入的不是一个函数则返回一个函数
    if (Object.prototype.toString.call(successCallback) !== '[object Function]') {
      successCallback = value => value;
    }
    // 如果没有传入failCallback, 或者传入的不是一个函数则返回一个函数
    if (Object.prototype.toString.call(failCallback) !== '[object Function]') {
      failCallback = reason => { throw reason };
    }

    // 实现 then 方法返回一个 Promise 对象
    // 创建 promise2 对象的时候，Promise 执行器是立即执行的。所以可以将逻辑放在执行器中
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 将代码改为异步执行，确保能够在 new Promise 执行完之后拿到 promise2
        setTimeout(() => {
          try {
            // 保存成功回调的返回值
            let x = successCallback(this.value);
            // 判断 x 的值是普通值还是 Promise 对象，
            // 如果是普通值，直接调用 resolve
            // 如果是 Promise 对象，查看这个 Promise 对象的返回结果，
            // 再根据返回结果来决定调用 resolve 还是 reject
            resolveRromise(promise2, x, resolve, reject);
          } catch (err) {
            // 将异常传递给下一个 then 方法的错误处理回调函数，
            reject(err);
          }
        }, 0);
      }
      // 失败状态
      else if (this.status === REJECTED) {
        // 将代码改为异步执行，确保能够在 new Promise 执行完之后拿到 promise2
        setTimeout(() => {
          try {
            // 保存失败回调的返回值
            let x = failCallback(this.reason);
            resolveRromise(promise2, x, resolve, reject);
          } catch (err) {
            // 将异常传递给下一个 then 方法的错误处理回调函数，
            reject(err);
          }
        }, 0);
      }
      // 等待状态, 将当前的两个(成功/失败)回调函数缓存,
      // 然后等到 promise 状态更新(fulfilled/rejected)的时候, 再执行回调函数.
      else {
        
        // 保存成功 和 失败的回调函数
        // 保存一个函数，这个函数里面执行成功的回调函数, 可以捕获回调函数执行过程中出现的异常
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              // 保存成功回调函数的的返回值
              let x = successCallback(this.value);
              resolveRromise(promise2, x, resolve, reject);
            } catch (err) {
              // 将异常传递给下一个 then 方法的错误处理回调函数，
              reject(err);
            }
          }, 0);
        });
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              // 保存失败回调的返回值
              let x = failCallback(this.reason);
              resolveRromise(promise2, x, resolve, reject);
            } catch (err) {
              // 将异常传递给下一个 then 方法的错误处理回调函数，
              reject(err);
            }
          }, 0);
        });
      }
    });

    // 返回 Promise 对象
    return promise2;
  }

  // finally方法，不管 Promise 状态是成功还是失败，它的回调函数都会被调用
  finally(callback) {
    // then 方法传入 成功和失败的回调，在其回调中执行 finally的回调函数
    return this.then(value => {
      // 这里并没有等待这个回调函数执行完成。
      // callback();
      // 在这里直接 return value，下一个 then 就立即执行了
      // return value;
      // 借助 resolve 静态方法, 将 callback 的返回值转为 Promise 对象
      // 然后将最终结果返回出去
      return MyPromise.resolve(callback())
        // 然后在 then 方法中等到 callback 执行完毕再将 value 返回
        .then(() => value);
    }, reason => {
      return MyPromise.resolve(callback())
        // 然后在 then 方法中等到 callback 执行完毕再将 reason 返回
        .then(() => { throw reason });
    });
  }

  // catch 方法是: 调用了(只注册了 失败回调 的) then 方法
  catch(failCallback) {
    return this.then(undefined, failCallback);
  }

  // 静态方法，可以直接使用 MyPromise.all
  // 接收一个数组
  static all(array) {
    // 定义 result，用来存储结果
    const result = [];
    // 记录已经执行完毕的数组元素个数
    let index = 0;
    // 返回一个 promise 对象
    return new MyPromise((resolve, reject) => {
      // 将数据添加到目标数据中
      function addRes(i, value) {
        result[i] = value;
        // 执行完毕+1
        index++;
        // 如果数组中所有元素都执行完毕，才 resolve 结果
        if (index === array.length) resolve(result);
      }
      // 循环数组，并判断每个数组元素是普通值还是 Promise 对象，
      // 如果是普通值，直接将元素放到结果集中，
      // 如果是Promise 对象，需要获取其返回值，并放到结果集中
      for (let i = 0; i < array.length; i++) {
        let current = array[i];
        // 判断是否是 promise 对象
        if (current instanceof MyPromise) {
          current.then(value => addRes(i, value), reason => reject(reason))
        } else {
          addRes(i, array[i]);
        }
      }
    })
  }

  // resolve 方法, 可以将一个值转为 Promise 对象.
  static resolve(value) {
    // 如果 value 已经是一个 promise 对象了, 原样返回
    if (value instanceof MyPromise) return value;
    // 使用resolve将 value 包裹
    // 并返回一个promise 对象
    return new MyPromise(resolve => resolve(value));
  }
}

function resolveRromise(promise2, x, resolve, reject) {
  // 判断是否是 自反回
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // 使用 instanceof 来判断 x 是否是 MyPromise 对象
  if (x instanceof MyPromise) {
    // 调用 then 方法来查看 x 的返回值，
    // 如果是成功的，就调用resolve；失败就调用 reject
    // x.then(value => resolve(value), reason => reject(reason))
    x.then(resolve, reject);
  } else {
    resolve(x)
  }
}

module.exports = MyPromise;
